import { EPSILON, EventsRes, SceneGraph } from '@amcax/base';
import { arePointsCollinear, calculatePoint } from '@app-cad/common/utils';
import { MessageTip } from '@app-cad/common/utils/MessageTip';
import { ctx2 } from '@app-cad/module';
import { wsApi } from '@app-cad/server';
import { Vector2, Vector3 } from 'three-legacy';
import {
  SKETCH_CENTEREVENTS,
  sketchEventCenter,
} from '../../../events/SketchEventCenter';
import CmdBase, { PickType } from '../../CmdBase';
import { Dimension } from '../dimension/Dimension';
import { ArcRabbetState } from './types';

interface SnapObject {
  curveId: string;
  pickType: PickType;
  point?: Vector3;
}

export default class CenterPointArcRabbetCmdInteractor extends CmdBase {
  private currentState = ArcRabbetState.START;

  private firstPoint: Vector3;
  private secondPoint: Vector3;
  private arcRadius: number;

  private thirdPoint: Vector3;
  private arcClockwise?: boolean;

  private radius = 0;

  private arcStartRadiusVec: Vector3;
  private arcEndRadiusVec: Vector3;

  private firstSnapPoint: SnapObject;
  private secondSnapPoint: SnapObject;
  private thirdSnapPoint: SnapObject;

  private dimArcRadius = new Dimension();
  private dimRadius = new Dimension();

  private isLoading = false;

  private get centerPoint() {
    if (this.firstPoint && this.secondPoint) {
      const midPoint = new Vector3()
        .addVectors(this.firstPoint, this.secondPoint)
        .multiplyScalar(0.5);

      if (this.thirdPoint) {
        if (
          arePointsCollinear([
            this.firstPoint,
            this.secondPoint,
            this.thirdPoint,
          ])
        ) {
          return midPoint;
        }

        const a2 = new Vector2(this.firstPoint.x, this.firstPoint.y);
        const b2 = new Vector2(this.secondPoint.x, this.secondPoint.y);
        const c2 = new Vector2(this.thirdPoint.x, this.thirdPoint.y);

        const abMid = new Vector2().addVectors(a2, b2).multiplyScalar(0.5);
        const acMid = new Vector2().addVectors(a2, c2).multiplyScalar(0.5);

        const abDir = new Vector2().subVectors(b2, a2).normalize();
        const acDir = new Vector2().subVectors(c2, a2).normalize();

        const abPerp = new Vector2(-abDir.y, abDir.x);
        const acPerp = new Vector2(-acDir.y, acDir.x);

        const denominator = abPerp.x * acPerp.y - abPerp.y * acPerp.x;

        const t =
          (acPerp.x * (abMid.y - acMid.y) - acPerp.y * (abMid.x - acMid.x)) /
          denominator;

        const center = new Vector2(
          abMid.x + t * abPerp.x,
          abMid.y + t * abPerp.y,
        );

        return new Vector3(center.x, center.y, 0);
      } else {
        return midPoint;
      }
    }
    return null;
  }

  private onMouseMove = (e: EventsRes) => {
    this.setMouseWV(e, true);
    if (this.currentState === ArcRabbetState.START) {
      this.firstPoint = this.mouseWV.clone();
      this.createAssistPoint(this.firstPoint, true, 0, true);
    } else if (this.currentState === ArcRabbetState.ONE_POINT) {
      this.secondPoint = this.mouseWV.clone();
      this.arcRadius = this.secondPoint.distanceTo(this.centerPoint);
    } else if (this.currentState === ArcRabbetState.TWO_POINTS) {
      this.thirdPoint = this.mouseWV.clone();
    } else if (this.currentState === ArcRabbetState.THREE_POINTS) {
      this.updateWidthDimension();
    }

    this.preview();
  };

  private onMouseDown = async (e: EventsRes) => {
    const snapPoint =
      this.pickType !== PickType.Null
        ? {
            pickType: this.pickType,
            curveId: this.pickSnapInfo.id,
            point: this.mouseWV.clone(),
          }
        : null;

    if (e.button.onLeft) {
      if (this.currentState === ArcRabbetState.START) {
        this.createAssistPoint(this.firstPoint, false, 0, true);
        this.previewLines.line = null;

        this.firstSnapPoint = snapPoint;

        this.currentState = ArcRabbetState.ONE_POINT;
      } else if (this.currentState === ArcRabbetState.ONE_POINT) {
        this.secondSnapPoint = snapPoint;

        this.currentState = ArcRabbetState.TWO_POINTS;
      } else if (this.currentState === ArcRabbetState.TWO_POINTS) {
        if (
          arePointsCollinear([
            this.firstPoint,
            this.secondPoint,
            this.thirdPoint,
          ])
        ) {
          MessageTip.warning('三点共线，无法绘制！');

          return;
        }

        this.thirdSnapPoint = snapPoint;

        this.currentState = ArcRabbetState.THREE_POINTS;
      } else if (this.currentState === ArcRabbetState.THREE_POINTS) {
        await this.finishDraw();
      }
    } else if (e.button.onRight && !this.closeRightClick) {
      this.currentState = ArcRabbetState.START;

      this.destroy();
    }
  };

  enterKeyDown = async () => {
    if (this.currentState === ArcRabbetState.TWO_POINTS) {
      this.dimArcRadius.determineDim();

      const midPoint = new Vector3()
        .addVectors(this.firstPoint, this.secondPoint)
        .multiplyScalar(0.5);
      const midToFirstDistance = this.firstPoint.distanceTo(midPoint);
      const newRadius = this.dimArcRadius.getDimInfo().value;
      if (newRadius < midToFirstDistance) {
        MessageTip.warning('半径太小，无法绘制圆弧');
        this.dimArcRadius.preview();

        return;
      }

      const midToCenterVec = midPoint.clone().sub(this.centerPoint).normalize();
      const height = Math.sqrt(newRadius ** 2 - midToFirstDistance ** 2);
      const newCenterPoint = midPoint.sub(
        midToCenterVec.multiplyScalar(height),
      );

      this.thirdPoint = calculatePoint(
        this.thirdPoint.clone().sub(newCenterPoint),
        this.dimArcRadius.getDimInfo().value,
        newCenterPoint,
      );

      this.arcRadius = newRadius;

      this.updateArcRadiusDimension();

      this.currentState = ArcRabbetState.THREE_POINTS;

      this.preview();
    } else if (this.currentState === ArcRabbetState.THREE_POINTS) {
      this.dimRadius.determineDim();
      this.radius = this.dimRadius.getDimInfo().value / 2;

      await this.finishDraw();
    }
  };

  constructor() {
    super();
    this.registerEvents();
  }

  reset() {
    this.currentState = ArcRabbetState.START;
    this.firstPoint = null;
    this.secondPoint = null;
    this.thirdPoint = null;
    this.arcClockwise = undefined;
    this.radius = 0;
    this.arcStartRadiusVec = null;
    this.arcEndRadiusVec = null;

    this.firstSnapPoint = null;
    this.secondSnapPoint = null;
    this.thirdSnapPoint = null;

    this.isLoading = false;
  }

  registerEvents() {
    this.dimArcRadius.reset();
    this.dimRadius.reset();

    sketchEventCenter()
      .setState('SketchCenterLineRabbet')
      .on(SKETCH_CENTEREVENTS.POINTERMOVE, this.onMouseMove)
      .on(SKETCH_CENTEREVENTS.POINTERDOWN, this.onMouseDown)
      .on(SKETCH_CENTEREVENTS.KEYUP, this.onKeyUp)
      .addExit(this.destroy.bind(this));
  }

  unregisterEvents() {
    sketchEventCenter()
      .off(SKETCH_CENTEREVENTS.POINTERMOVE, this.onMouseMove)
      .off(SKETCH_CENTEREVENTS.POINTERDOWN, this.onMouseDown)
      .off(SKETCH_CENTEREVENTS.KEYUP, this.onKeyUp)
      .setDefault();
  }

  releaseObjects() {
    this.dimArcRadius.destroy();
    this.dimRadius.destroy();

    this.clearLine();
    this.assisGroup.clear();
    SceneGraph.emptyGroup(this.assisGroup);
    ctx2.viewer.requestRender();
  }

  private updateArcRadiusDimension() {
    this.createAssistPoint(this.secondPoint, true, 1);
    const arc = this.createArc(
      this.centerPoint,
      this.firstPoint,
      this.secondPoint,
      this.arcRadius,
      this.arcClockwise,
    );

    this.createAssistPoint(this.centerPoint, true, 2);
    this.createAssistPoint(this.thirdPoint, true, 3);

    this.createUpdateLine(arc, 'aline', 1, {
      dashSize: 5,
      gapSize: 3,
    });

    this.createUpdateLine([this.centerPoint, this.secondPoint], 'rline', 2, {
      dashSize: 5,
      gapSize: 3,
    });

    this.arcRadius = this.secondPoint.distanceTo(this.centerPoint);
    this.arcStartRadiusVec = this.firstPoint
      .clone()
      .sub(this.centerPoint)
      .normalize();
    this.arcEndRadiusVec = this.secondPoint
      .clone()
      .sub(this.centerPoint)
      .normalize();

    this.arcClockwise =
      this.secondPoint
        .clone()
        .sub(this.firstPoint)
        .cross(this.thirdPoint.clone().sub(this.firstPoint)).z > 0;

    this.dimArcRadius.dimLine([this.centerPoint, this.secondPoint]);
  }

  private updateWidthDimension() {
    this.radius =
      this.arcRadius - this.mouseWV.clone().distanceTo(this.centerPoint);

    if (Math.abs(this.radius) > this.arcRadius) {
      this.radius = this.arcRadius * Math.sign(this.radius);
      if (this.radius === -this.arcRadius) {
        this.radius = -this.arcRadius + EPSILON;
      }
    }

    const arcEdge1 = this.centerPoint
      .clone()
      .add(
        this.arcEndRadiusVec
          .clone()
          .multiplyScalar(this.arcRadius - this.radius),
      );

    const arcEdge2 = this.secondPoint.clone().multiplyScalar(2).sub(arcEdge1);

    this.createAssistPoint(arcEdge1, true, 4);

    this.dimRadius.dimLine([arcEdge1, arcEdge2]);
  }

  private preview() {
    if (this.currentState === ArcRabbetState.START) {
      return;
    }

    if (this.currentState === ArcRabbetState.ONE_POINT) {
      this.createUpdateLine(
        this.createSemiCircle(
          this.centerPoint,
          this.arcRadius,
          this.secondPoint.clone().sub(this.centerPoint),
        ),
        'aline',
        2,
        {
          dashSize: 5,
          gapSize: 3,
        },
      );
    } else if (this.currentState === ArcRabbetState.TWO_POINTS) {
      this.updateArcRadiusDimension();
    } else if (this.currentState === ArcRabbetState.THREE_POINTS) {
      const radius = Math.abs(this.radius);

      const vectors: Vector3[] = [];

      const semiCircle1 = this.createSemiCircle(
        this.firstPoint,
        radius,
        this.arcStartRadiusVec
          .clone()
          .multiplyScalar(this.arcClockwise ? 1 : -1),
      );

      const arc1 = this.createArc(
        this.centerPoint,
        this.firstPoint
          .clone()
          .add(this.arcStartRadiusVec.clone().multiplyScalar(radius)),
        this.secondPoint
          .clone()
          .add(this.arcEndRadiusVec.clone().multiplyScalar(radius)),
        this.arcRadius - radius,
        this.arcClockwise,
      );

      const semiCircle2 = this.createSemiCircle(
        this.secondPoint,
        radius,
        this.arcEndRadiusVec.clone().multiplyScalar(this.arcClockwise ? -1 : 1),
      );

      const arc2 = this.createArc(
        this.centerPoint,
        this.secondPoint
          .clone()
          .sub(this.arcEndRadiusVec.clone().multiplyScalar(radius)),
        this.firstPoint
          .clone()
          .sub(this.arcStartRadiusVec.clone().multiplyScalar(radius)),
        this.arcRadius + radius,
        !this.arcClockwise,
      );

      if (this.arcClockwise) {
        vectors.push(...semiCircle1);
        vectors.push(...arc1);
        vectors.push(...semiCircle2);
        vectors.push(...arc2);
      } else {
        vectors.push(...semiCircle1.reverse());
        vectors.push(...arc1);
        vectors.push(...semiCircle2.reverse());
        vectors.push(...arc2);
      }

      this.createUpdateLine(vectors, 'line');
    }

    ctx2.viewer.requestRender();
  }

  private async finishDraw() {
    const param = {
      mode: 'three-point',
      width: Math.abs(this.radius) * 2,
      start: [this.firstPoint.x, this.firstPoint.y],
      mid: [this.thirdPoint.x, this.thirdPoint.y],
      end: [this.secondPoint.x, this.secondPoint.y],
      sense: !this.arcClockwise,
      constraints: this.addConstraint(),
      autoValue1: !this.dimRadius.addDimension(),
      autoValue2: !this.dimArcRadius.addDimension(),
      curveStyle: this.lineType,
    };
    try {
      if (this.isLoading) {
        return;
      }
      try {
        this.isLoading = true;
        const res = await wsApi.createRabbet(this.sketchId, param);
        await this.render({ added: res.added }, true);
      } finally {
        this.isLoading = false;
      }
    } catch (error) {
      //
    }
    this.reset();
    this.releaseObjects();
  }

  private addConstraint() {
    const getConstraintFromSnapPoint = ([snapPoint, point]: [
      SnapObject,
      Vector3,
    ]) =>
      snapPoint?.curveId ? this.getConstraint(snapPoint, point, true) : null;

    const snapPointPointPairs: [SnapObject, Vector3][] = [
      [this.firstSnapPoint, this.firstPoint],
      [this.secondSnapPoint, this.secondPoint],
      [this.thirdSnapPoint, this.thirdPoint],
    ];

    return snapPointPointPairs.map(getConstraintFromSnapPoint).filter(Boolean);
  }
}
